package com.declum.archon.chunkserver;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.Properties;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.GnuParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;
import org.apache.mina.common.ByteBuffer;
import org.apache.mina.common.IoAcceptor;
import org.apache.mina.common.SimpleByteBufferAllocator;
import org.apache.mina.transport.socket.nio.SocketAcceptor;
import org.apache.mina.transport.socket.nio.SocketAcceptorConfig;

import com.declum.archon.chunkserver.handler.ChunkServerHandler;
import com.declum.archon.chunkserver.manager.ChunkManager;
import com.declum.archon.chunkserver.manager.IChunkManager;
import com.declum.archon.chunkserver.manager.disk.DiskManager;
import com.declum.archon.chunkserver.manager.disk.IDiskManager;
import com.declum.archon.chunkserver.server.ChunkServer;

/**
 * {@link ChunkServerLauncher} is used to launch the ChunkServer instance
 * 
 * @author Clement
 * 
 */
public class ChunkServerLauncher {
	private static Logger		logger;
	private static int			port;
	private static Properties	properties;

	/**
	 * This prints the required command line argument to the console
	 * 
	 * @param opts
	 *            options to be printed
	 */
	private static void printUsage(Options opts) {
		HelpFormatter formatter = new HelpFormatter();
		formatter.printHelp("ChunkServer", opts);
	}

	/**
	 * Main method for the program
	 * 
	 * @param args
	 *            command line argument
	 * @throws Exception
	 *             if any exception occurred while launching the application
	 */
	public static void main(String[] args) throws Exception {

		/**
		 * Initialize the Logger
		 */
		ChunkServerLauncher.logger = Logger
				.getLogger(ChunkServerLauncher.class);
		/**
		 * Initializes the GnuParser for parsing the command line argument
		 */
		CommandLineParser parser = new GnuParser();

		Options opts = new Options();
		opts.addOption("p", "port", true,
				"Port number at which the server has to listen");
		opts.addOption("l", "logconfig", true, "Log4j configuration file");
		opts.addOption("c", "config", true, "Property file for ChunkServer");
		CommandLine line = null;

		try {
			line = parser.parse(opts, args);
		} catch (ParseException exp) {
			System.err.println("Parsing failed.  Reason: " + exp.getMessage());
		}
		String strPort = null;
		ChunkServerLauncher.port = 12123;

		if (line == null) {
			System.exit(-1);
			return;
		}

		String propertyFileLocation = line.getOptionValue("c");
		if (propertyFileLocation == null) {
			ChunkServerLauncher.printUsage(opts);
			System.exit(0);
			return;
		}

		/**
		 * Check whether the config file exists
		 */
		File file = new File(propertyFileLocation);
		if (!file.exists()) {
			System.out.println("Property file " + file.getAbsolutePath()
					+ " does not exists");
			System.exit(0);
			return;
		}

		ChunkServerLauncher.properties = new Properties();
		ChunkServerLauncher.properties.load(new FileInputStream(file));

		String configPropertyFile = line.getOptionValue("l");
		if (configPropertyFile == null) {
			ChunkServerLauncher.printUsage(opts);
			System.exit(0);
			return;
		}

		/**
		 * Check whether the config file exists
		 */
		file = new File(configPropertyFile);
		if (!file.exists()) {
			System.out.println("Config file " + file.getAbsolutePath()
					+ " does not exists");
			System.exit(0);
			return;
		}
		/**
		 * Configure the Logger object
		 */
		PropertyConfigurator.configure(configPropertyFile);

		strPort = line.getOptionValue("p", (12123 + ""));
		try {
			ChunkServerLauncher.port = Integer.parseInt(strPort);
		} catch (Exception e) {
			ChunkServerLauncher.printUsage(opts);
		}

		/**
		 * Was not knowing why this is for
		 */
		ByteBuffer.setUseDirectBuffers(false);

		/**
		 * I think for Heap Allocation
		 */
		ByteBuffer.setAllocator(new SimpleByteBufferAllocator());

		/**
		 * Create a Acceptor
		 */
		IoAcceptor acceptor = new SocketAcceptor();

		/**
		 * Create a SocketAccepterConfig to configure the Server Socket
		 */
		SocketAcceptorConfig cfg = new SocketAcceptorConfig();

		/**
		 * Add a logger hander in the Chain
		 */
		// cfg.getFilterChain().addLast("logger", new LoggingFilter());

		/**
		 * Add the Text decoder/encoder to the chain, this processes the request
		 * and call ChunkServer handler for each line
		 */
		// cfg.getFilterChain().addLast("codec",new ProtocolCodecFilter(new
		// CodecFactory()));

		/**
		 * This is our Custom Handler for the Service
		 */
		ChunkServerHandler lockingHandler = new ChunkServerHandler();
		/**
		 * get the Chunk Server
		 */
		ChunkServer chunkServer = ChunkServerLauncher.getChunkServer();

		lockingHandler.setChunkServer(chunkServer);

		/**
		 * Bind the Server to a port
		 */
		acceptor.bind(new InetSocketAddress(ChunkServerLauncher.port),
				lockingHandler, cfg);
		ChunkServerLauncher.logger
				.debug("Archon ChunkServer started. Listening at "
						+ ChunkServerLauncher.port);
	}

	/**
	 * Returns disk manager instance
	 * 
	 * @return
	 * @throws IOException
	 */
	private static DiskManager getDiskManager() throws IOException {
		ChunkServerLauncher.logger.debug("initializing disk manager");
		/**
		 * Create disk manager instance
		 */
		DiskManager diskManager = new DiskManager();
		String property = ChunkServerLauncher.properties.getProperty("disks");
		/**
		 * Configured the disk specified inthe config file
		 */
		if (property != null) {
			String[] split = property.split(" ");
			if (split.length > 0) {
				for (String disk : split) {
					ChunkServerLauncher.logger
							.debug("gathering information from disk " + disk);
					diskManager.addDisk(disk);
				}
			} else {
				throw new IOException("No disk configured");
			}
		} else {
			throw new IOException("No disk information found");
		}
		return diskManager;
	}

	/**
	 * It returns the ChunkManger instance
	 * 
	 * @return
	 * @throws IOException
	 */
	private static ChunkManager getChunkManager() throws IOException {
		ChunkServerLauncher.logger.debug("initializing chunk manager");
		ChunkManager chunkManager = new ChunkManager();

		/**
		 * Assign disk manager
		 */
		IDiskManager diskManager = ChunkServerLauncher.getDiskManager();
		chunkManager.setDiskManager(diskManager);
		return chunkManager;
	}

	/**
	 * It returns the ChunkServer instance based on the property file.
	 * 
	 * @return
	 * @throws IOException
	 */
	private static ChunkServer getChunkServer() throws IOException {
		ChunkServerLauncher.logger.debug("initializing chunk server");
		ChunkServer chunkServer = new ChunkServer();

		/**
		 * Assign the name based on IP and Port
		 */
		chunkServer.setName(InetAddress.getLocalHost().getHostAddress() + ":"
				+ ChunkServerLauncher.port);
		String clusterID = ChunkServerLauncher.properties
				.getProperty("clusterid");
		if (clusterID == null) {
			throw new IOException("ClusterID is required");
		}
		/**
		 * Assign cluster ID
		 */
		chunkServer.setClusterID(clusterID);

		/**
		 * Assign ChunkManager
		 */
		IChunkManager chunkManager = ChunkServerLauncher.getChunkManager();
		chunkServer.setChunkManager(chunkManager);
		return chunkServer;
	}
}
